package nachos.threads;

import nachos.machine.*;
import java.util.*;

/**
 * Uses the hardware timer to provide preemption, and to allow threads to sleep
 * until a certain time.
 */
public class Alarm {
    /**
     * Allocate a new Alarm. Set the machine's timer interrupt handler to this
     * alarm's callback.
     *
     * <p><b>Note</b>: Nachos will not function correctly with more than one
     * alarm.
     */
    public Alarm() {
        sleepingThreads = new LinkedHashMap();
        justThreads = new KThread[10];
        justWakeTimes = new Long[10];
	Machine.timer().setInterruptHandler(new Runnable() {
		public void run() { timerInterrupt(); }
	    });
    }

    /**
     * The timer interrupt handler. This is called by the machine's timer
     * periodically (approximately every 500 clock ticks). Causes the current
     * thread to yield, forcing a context switch if there is another thread
     * that should be run.
     */
    public void timerInterrupt() {
	KThread.currentThread().yield();
        if(!sleepingThreads.isEmpty())
            for(int i = 0; i <sleepingThreads.size(); i++){
                if(justWakeTimes[i] < Machine.timer().getTime()){
                    justThreads[i].awake();
                    sleepingThreads.remove(justThreads[i]);
                }
            }
    }

    /**
     * Put the current thread to sleep for at least <i>x</i> ticks,
     * waking it up in the timer interrupt handler. The thread must be
     * woken up (placed in the scheduler ready set) during the first timer
     * interrupt where
     *
     * <p><blockquote>
     * (current time) >= (WaitUntil called time)+(x)
     * </blockquote>
     *
     * @param	x	the minimum number of clock ticks to wait.
     *
     * @see	nachos.machine.Timer#getTime()
     */
    public void waitUntil(long x) {
	// for now, cheat just to get something working (busy waiting is bad)
        if(x > 0){
            Long wakeTime = Machine.timer().getTime() + x;
            sleepingThreads.put(KThread.currentThread(), wakeTime);
            justThreads = sleepingThreads.keySet().toArray(justThreads);
            justWakeTimes = sleepingThreads.values().toArray(justWakeTimes);
            /*while (wakeTime > Machine.timer().getTime())
                KThread.yield();*/
            boolean intStatus = Machine.interrupt().disable();
            KThread.currentThread().sleep();
            Machine.interrupt().restore(intStatus);
        }
    }

    private LinkedHashMap<KThread,Long> sleepingThreads;
    private KThread[] justThreads;
    private Long[] justWakeTimes;
}
